﻿#include "pch.h"
#include "sbcs.h"
#include "extend/sbvalues.h"
#include "kstub_math.h"
// struct timeval
#if _SB_WINDOWS_
#include <winsock2.h>
#else
#include "sys/time.h"
#endif

/**
 * 로컬 시간으로 변화.
 * @param [반환]	ptm	시간 구조체.
 * @param	tt	   	time_t 형식의 시간.
 */
void k_localtime(struct tm* ptm, const time_t tt)
{
#if _MSC_VER
	localtime_s(ptm, &tt);
#elif __GNUC__
	localtime_r(&tt, ptm);
#else
	if (ptm)
		*ptm = *localtime(&tt);
#endif
}

/**
 * UTC 시간으로 변화.
 * @param [반환]	ptm	시간 구조체.
 * @param	tt	   	time_t 형식의 시간.
 */
void k_gmtime(struct tm* ptm, const time_t tt)
{
#if _MSC_VER
	gmtime_s(ptm, &tt);
#elif __GNUC__
	gmtime_r(&tt, ptm);
#else
	if (ptm)
		ptm = gmtime(&tt);
#endif
}

/**
 * 현재 시간.
 * @param [반환]	tv	시간 구조체.
 */
void k_timeval_now(struct timeval* tv)
{
#if _SB_WINDOWS_
	union
	{
		FILETIME	ft;
		kulong		l;
	} t;

	k_return_if_fail(tv != NULL);

	GetSystemTimeAsFileTime(&t.ft);

	t.l -= K_CONST_LONG(116444736000000000);
	t.l /= 10;

	tv->tv_sec = (kint)(t.l / 1000000);
	tv->tv_usec = (kint)(t.l % 1000000);
#else
	struct timeval v;

	gettimeofday(&v, NULL);

	tv->tv_sec = v.tv_sec;
	tv->tv_usec = v.tv_usec;
#endif
}

/**
 * 시간 구조체에 마이크로초 더하기.
 * @param [입력,반환]	tv  	시간 구조체.
 * @param	microseconds	마이크로초 단위인 microsecond.
 */
void k_timeval_add_micro(struct timeval* tv, kint microseconds)
{
	k_return_if_fail(tv->tv_usec >= 0 && tv->tv_usec < K_CONST_USEC_PER_SEC);

	if (microseconds < 0)
	{
		microseconds *= -1;
		tv->tv_usec -= microseconds % K_CONST_USEC_PER_SEC;
		tv->tv_sec -= microseconds / K_CONST_USEC_PER_SEC;

		if (tv->tv_usec < 0)
		{
			tv->tv_usec += K_CONST_USEC_PER_SEC;
			tv->tv_sec--;
		}
	}
	else
	{
		tv->tv_usec += microseconds % K_CONST_USEC_PER_SEC;
		tv->tv_sec += microseconds / K_CONST_USEC_PER_SEC;

		if (tv->tv_usec >= K_CONST_USEC_PER_SEC)
		{
			tv->tv_usec -= K_CONST_USEC_PER_SEC;
			tv->tv_sec++;
		}
	}
}

/**
 * 시간 구조체에 밀리초 더하기.
 * @param [입력,반환]	tv  	시간 구조체.
 * @param	milliseconds	밀리초 단위인 millisecond.
 */
void k_timeval_add_milli(struct timeval* tv, kint milliseconds)
{
	k_timeval_add_micro(tv, milliseconds * (K_CONST_NSEC_PER_SEC / K_CONST_USEC_PER_SEC));
}

/**
 * 현재 날짜 시간.
 * @param [반환]	dt	(널값이 아니면) 현재 날짜 시간.
 */
void k_now(kDateTime* dt)
{
#if _SB_WINDOWS_
	SYSTEMTIME st;

	k_return_if_fail(dt != NULL);

	GetLocalTime(&st);

	dt->year = st.wYear;
	dt->month = st.wMonth;
	dt->day = st.wDay;
	dt->dow = st.wDayOfWeek;
	dt->hour = st.wHour;
	dt->minute = st.wMinute;
	dt->second = st.wSecond;
	dt->millisecond = st.wMilliseconds;
#else
	struct timeval tv;
	struct tm tm;

	k_return_if_fail(dt != NULL);

	gettimeofday(&tv, NULL);
	k_localtime(&tm, tv.tv_sec);

	dt->year = tm.tm_year + 1900;
	dt->month = tm.tm_mon + 1;
	dt->day = tm.tm_mday;
	dt->dow = tm.tm_wday;
	dt->hour = tm.tm_hour;
	dt->minute = tm.tm_min;
	dt->second = tm.tm_sec;
	dt->millisecond = tv.tv_usec / 1000;
#endif
}

/**
 * 현재의 UTC 날짜 시간.
 * @param [반환]	dt	(널값이 아니면) 현재 날짜 시간.
 */
void k_utc(kDateTime* dt)
{
#if _SB_WINDOWS_
	SYSTEMTIME st;

	k_return_if_fail(dt != NULL);

	GetSystemTime(&st);

	dt->year = st.wYear;
	dt->month = st.wMonth;
	dt->day = st.wDay;
	dt->dow = st.wDayOfWeek;
	dt->hour = st.wHour;
	dt->minute = st.wMinute;
	dt->second = st.wSecond;
	dt->millisecond = st.wMilliseconds;
#else
	struct timeval tv;
	struct tm tm;

	k_return_if_fail(dt != NULL);

	gettimeofday(&tv, NULL);
	k_gmtime(&tm, tv.tv_sec);

	dt->year = tm.tm_year + 1900;
	dt->month = tm.tm_mon + 1;
	dt->day = tm.tm_mday;
	dt->dow = tm.tm_wday;
	dt->hour = tm.tm_hour;
	dt->minute = tm.tm_min;
	dt->second = tm.tm_sec;
	dt->millisecond = tv.tv_usec / 1000;
#endif
}

/**
 * 초를 시간으로.
 * @param	sec   	초.
 * @param [반환]	dt	(널값이 아니면) 변환된 시간.
 */
void k_stod(double sec, kDateTime* dt)
{
	kuint ns = (kuint)sec;
	double m;

	dt->hour = ns / 3600;
	dt->minute = (ns % 3600) / 60;
	dt->second = (ns % 60);

	m = (double)stub_math_floor(sec);
	m = sec - m;
	m *= 1000.0;
	dt->millisecond = (kuint)m;
}

/**
 * 밀리초를 시간으로.
 * @param	msec  	밀리초.
 * @param [반환]	dt	(널값이 아니면) 변환된 시간.
 */
void k_mstod(kuint msec, kDateTime* dt)
{
	kuint ns = msec / 1000;

	dt->hour = ns / 3600;
	dt->minute = (ns % 3600) / 60;
	dt->second = (ns % 60);
	dt->millisecond = msec - (ns * 1000);
}

/**
 * 날자를 문자열로 바꾼다.
 * @param	dt	   	주어진 날짜 시간.
 * @param [반환]	buf	(널값이 아니면) 버퍼.
 * @param	size   	버퍼의 크기.
 * @param	date   	날짜를 넣으려면 참으로 넣는다.
 * @param	time   	시간을 넣으려면 참으로 넣는다.
 * @param	milli  	밀리초를 넣으려면 참으로 넣는다.
 */
void k_dtos(const kDateTime* dt, char* buf, kint size, kcham date, kcham time, kcham milli)
{
	kDateTime tmp;

	k_return_if_fail(buf != NULL && size > 0);

	if (!dt)
	{
		k_now(&tmp);
		dt = &tmp;
	}

	if (date)
	{
		if (time)
		{
			if (milli)
			{
				k_snprintf(buf, size, "%04d-%02d-%02d %02d:%02d:%02d.%03d",
					dt->year, dt->month, dt->day,
					dt->hour, dt->minute, dt->second, dt->millisecond);
			}
			else
			{
				k_snprintf(buf, size, "%04d-%02d-%02d %02d:%02d:%02d",
					dt->year, dt->month, dt->day,
					dt->hour, dt->minute, dt->second);
			}
		}
		else
		{
			k_snprintf(buf, size, "%04d-%02d-%02d",
				dt->year, dt->month, dt->day);
		}
	}
	else
	{
		if (time)
		{
			if (milli)
			{
				k_snprintf(buf, size, "%02d:%02d:%02d.%03d",
					dt->hour, dt->minute, dt->second, dt->millisecond);
			}
			else
			{
				k_snprintf(buf, size, "%02d:%02d:%02d",
					dt->hour, dt->minute, dt->second);
			}
		}
		else
		{
			if (milli)
				k_snprintf(buf, size, "%03d", dt->millisecond);
			else
				k_snprintf(buf, size, "(inval)");
		}
	}
}


//////////////////////////////////////////////////////////////////////////
// GUID

/**
 * GUID 값 또는 내용을 가진 인수 guids.
 * @param [반환]	guid	(널값이 아니면) GUID.
 */
void k_guid(kGuid* guid)
{
	kDateTime dt;

	k_return_if_fail(guid);

	k_now(&dt);

	guid->A = dt.day | (dt.hour << 16);
	guid->B = dt.month | (dt.second << 16);
	guid->C = dt.millisecond | (dt.minute << 16);
	guid->D = dt.year ^ k_cycle();
}

/**
 * GUID 같나 비교.
 * @param	l	GUID 왼쪽 인수.
 * @param	r	GUID 오른쪽 인수.
 * @return	참 거짓 값.
 */
kint k_guid_eq(const kGuid* l, const kGuid* r)
{
	kuint
		a = l->A ^ r->A,
		b = l->B ^ r->B,
		c = l->C ^ r->C,
		d = l->D ^ r->D;
	return (a | b | c | d) == 0;
}

/**
 * GUID 비교.
 * @param	l	GUID 왼쪽 인수.
 * @param	r	GUID 오른쪽 인수.
 * @return	C 형식 비교 값 (-1, 0, 1).
 */
kint k_guid_comp(const kGuid* l, const kGuid* r)
{
	return
		(l->A < r->A) ? 1 : (l->A > r->A) ? -1 :
		(l->B < r->B) ? 1 : (l->B > r->B) ? -1 :
		(l->C < r->C) ? 1 : (l->C > r->C) ? -1 :
		(l->D < r->D) ? 1 : (l->D > r->D) ? -1 : 0;
}

/**
 * GUID를 문자열로 바꿈.
 * @param	guid   	GUID.
 * @param	mtd	   	메소드.
 * @param [반환]	buf	(널값이 아닌) 버퍼.
 * @param	size   	버퍼 크기.
 * @return	문제가 있거나 실패하면 널값을 반환, 일반적으로 버퍼 포인터.
 */
char* k_guid_tos(const kGuid* guid, kint mtd, char* buf, kint size)
{
	kGuid tmpguid;

	if (!buf || size <= 0)
		return NULL;

	if (!guid)
	{
		k_guid(&tmpguid);
		guid = &tmpguid;
	}

	switch (mtd)
	{
		case KGUID_UNIQUE:
			k_snprintf(buf, size, "%08X-%08X-%08X-%08X", guid->A, guid->B, guid->C, guid->D);
			break;

		case KGUID_DIGIT:
			k_snprintf(buf, size, "%08X-%04X-%04X-%04X-%04X%08X", guid->A, guid->B >> 16, guid->B & 0xFFFF, guid->C >> 16, guid->C & 0xFFFF, guid->D);
			break;

		case KGUID_DIGIT_BRACE:
			k_snprintf(buf, size, "{%08X-%04X-%04X-%04X-%04X%08X}", guid->A, guid->B >> 16, guid->B & 0xFFFF, guid->C >> 16, guid->C & 0xFFFF, guid->D);
			break;

		case KGUID_GIDIT_PARENTHESE:
			k_snprintf(buf, size, "(%08X-%04X-%04X-%04X-%04X%08X)", guid->A, guid->B >> 16, guid->B & 0xFFFF, guid->C >> 16, guid->C & 0xFFFF, guid->D);
			break;

		default:
			k_snprintf(buf, size, "%08X%08X%08X%08X", guid->A, guid->B, guid->C, guid->D);
			break;
	}

	return buf;
}


//////////////////////////////////////////////////////////////////////////
// 틱

/**
 * 사이클 32비트.
 * @return	현재의 사이클 TICK.
 */
kuint k_cycle(void)
{
#if _SB_WINDOWS_
	LARGE_INTEGER l;
	QueryPerformanceCounter(&l);
	return l.LowPart;
#else
	struct timeval tv;
	gettimeofday(&tv, NULL);
	return (kuint)((((kulong)tv.tv_sec) * K_CONST_ULONG(1000000)) + (((kulong)tv.tv_usec)));
#endif
}

/**
 * 사이클 64비트.
 * @return	현재의 사이클 TICK.
 */
kulong k_cycle_long(void)
{
#if _SB_WINDOWS_
	LARGE_INTEGER ll;
	QueryPerformanceCounter(&ll);
	return ll.QuadPart;
#elif _SB_BSD_
	kulong n;
	struct timespec tp;

	if (clock_gettime(CLOCK_REALTIME, &tp) == 0)
		n = ((kulong)tp.tv_sec * 1000) + ((kulong)tp.tv_nsec / 1000000);
	else
	{
		struct timeval tv;
		gettimeofday(&tv, 0);
		n = ((kulong)tv.tv_sec * 1000) + ((kulong)tv.tv_usec / 1000);
	}

	return n;
#else
	kulong n;
	struct timeval tv;
	gettimeofday(&tv, 0);
	n = (kulong)tv.tv_sec * 1000 + (kulong)tv.tv_usec / 1000;
	return n;
#endif
}

/**
 * 사이클 TICK 주기.
 * @return	현재의 주기.
 */
double k_cycle_per_sec(void)
{
#if _SB_WINDOWS_
	static kcham s_init = FALSE;
	static double s_value = 0.001;

	if (!s_init)
	{
		LARGE_INTEGER ll;

		if (QueryPerformanceFrequency(&ll))
			s_value = 1.0 / (double)ll.QuadPart;

		s_init = TRUE;
	}

	return s_value;
#else
	return 0.001;
#endif
}

/**
 * 초단위 TICK.
 * @return	현재의 TICK.
 */
double k_tick_second(void)
{
	double t = (double)k_cycle_long();
	double p = k_cycle_per_sec();
	return t * p;
}

/**
 * 밀리초 단위의 TICK.
 * @return	현재의 TICK.
 */
kuint k_tick(void)
{
#if _SB_WINDOWS_
	klong l = (klong)(k_tick_second() * 1000.0);
	return (kuint)(l & 0xFFFFFFFF);
#else
	return k_cycle();
#endif
}

/**
 * 밀리초 단위의 TICK 64비트 형태.
 * @return	현재의 TICK.
 */
kulong k_tick_long(void)
{
#if _SB_WINDOWS_
	klong l = (klong)(k_tick_second() * 1000.0);
	return l;
#else
	return k_cycle_long();
#endif
}


//////////////////////////////////////////////////////////////////////////
// 슬립

/**
 * 밀리초 슬립.
 * @param	milliseconds	밀리초 단위로 처리되는 millisecond.
 */
void k_sleep(kuint milliseconds)
{
#if _SB_WINDOWS_
#if _SB_WINDOWS_DESKTOP_
	Sleep(milliseconds);
#else
	// 가져옴: Microsoft thread emulation
	static HANDLE sh=NULL;
	HANDLE eh = sh;

	if (!eh)
	{
		eh = CreateEventEx(NULL, NULL, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS);

		if (!eh)
			return;

		HANDLE pe = InterlockedCompareExchangePointerRelease(&sh, eh, NULL);

		if (pe)
		{
			CloseHandle(eh);
			eh = pe;
		}
	}

	WaitForSingleObjectEx(eh, milliseconds, FALSE);
#endif
#else
	uint s = milliseconds / 1000;
	uint u = (milliseconds % 1000) * 1000;
	sleep(s);
	usleep(u);
#endif
}

/**
 * 마이크로초 슬립.
 * @param	microseconds	마이크로초 단위로 처리되는 microsecond.
 */
void k_usleep(kuint microseconds)
{
#if _SB_WINDOWS_
	k_sleep(microseconds / 1000);
#else
	usleep(microseconds);
#endif
}

/**
 * 초 슬립.
 * @param	seconds	초 단위로 처리되는 second.
 */
void k_ssleep(kuint seconds)
{
	k_sleep(seconds * 1000);
}

/**
 * 마이크로 슬립, 정밀 시계를 이용하며 스레드 콘텍스트가 일반 슬립보다 제한된다.
 * @param	microseconds	마이크로초 단위로 처리되는 microsecond.
 */
void k_msleep(kulong microseconds)
{
#if _SB_WINDOWS_
	double dms = (double)microseconds;
	LARGE_INTEGER t1, t2, freq;

	if (!QueryPerformanceFrequency(&freq))
		k_usleep((kuint)microseconds);
	else
	{
		QueryPerformanceCounter(&t1);

		do
		{
#if _SB_WINDOWS_DESKTOP_
			// XP 이상 지원.
			// ... 참고로 이 함수 대단히 무겁다고 함.
			SwitchToThread();
#endif
			QueryPerformanceCounter(&t2);
		} while (((double)(t2.QuadPart - t1.QuadPart) / freq.QuadPart * 1000000) < dms);
	}
#elif _SB_UNIX_
	struct timespec ts;
	ts.tv_sec = microseconds / 1000000;
	ts.tv_nsec = (microseconds % 1000000) * 1000;

	while (nanosleep(&ts, &ts))
	{
		if (errno != EINTR)
			break;
	}
#else
	usleep(microseconds);
#endif
}


//////////////////////////////////////////////////////////////////////////
// 타이머
typedef struct kRealTimer
{
	kTimer				base;

	kcham				stop;
	kint				past;
	double              cut;

	kvlong				basetime;
	kvlong				stoptime;
	kvlong				lasttime;
	kvlong				curtime;
	kvlong				frmtime;

	kuint				count;

	double				tick;
	kvlong				frame;

	kcham				manual;
	double				fps_abs;
	kint				fps_frame;
} kRealTimer;

/**
 * 타이머 만들기.
 * @return	문제가 있거나 실패하면 널값을 반환, 성공할 때 반환값은 만들어진 타이머.
 */
kTimer* k_timer_new(void)
{
	kRealTimer* self;
#if _SB_WINDOWS_
	LARGE_INTEGER ll;
#endif

	self = k_new_0(kRealTimer);

#if _SB_WINDOWS_
	if (!QueryPerformanceFrequency(&ll))
	{
		self->frame.q = 1000;
		self->tick = 0.001;
	}
	else
	{
		self->frame.q = ll.QuadPart;
		self->tick = 1.0 / (double)ll.QuadPart;
	}
#else
	self->frame.q = 1000;
	self->tick = 0.001;
#endif

	self->curtime.q = k_cycle_long();
	self->basetime.q = self->curtime.q;
	self->lasttime.q = self->curtime.q;
	self->count = self->curtime.dw.l;

	self->cut = 9999999.0;  //10.0;

	return (kTimer*)self;
}

/**
 * 타이머 제거.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 */
void k_timer_delete(kTimer* self)
{
	k_free(self);
}

/**
 * 타이머 리셋.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 */
void k_timer_reset(kTimer* self)
{
	kRealTimer* impl = (kRealTimer*)self;

	impl->curtime.q = (impl->stoptime.q != 0) ? impl->stoptime.q : k_cycle_long();
	impl->basetime.q = impl->curtime.q;
	impl->lasttime.q = impl->curtime.q;
	impl->stoptime.q = 0;
	impl->count = impl->curtime.dw.l;

	impl->stop = FALSE;
}

/**
 * 타이머 시작.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 */
void k_timer_start(kTimer* self)
{
	kRealTimer* impl = (kRealTimer*)self;

	impl->curtime.q = (impl->stoptime.q != 0) ? impl->stoptime.q : k_cycle_long();

	if (impl->stop)
		impl->basetime.q += impl->curtime.q - impl->stoptime.q;

	impl->lasttime.q = impl->curtime.q;
	impl->stoptime.q = 0;
	impl->count = impl->curtime.dw.l;

	impl->stop = FALSE;
}

/**
 * 타이머 정지.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 */
void k_timer_stop(kTimer* self)
{
	kRealTimer* impl = (kRealTimer*)self;

	impl->curtime.q = (impl->stoptime.q != 0) ? impl->stoptime.q : k_cycle_long();
	impl->lasttime.q = impl->curtime.q;
	impl->stoptime.q = impl->curtime.q;
	impl->count = impl->curtime.dw.l;

	impl->stop = TRUE;
}

/**
 * 타이머 갱신.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	성공하면 참, 실패하면 거짓.
 */
kcham k_timer_update(kTimer* self)
{
	kRealTimer* impl = (kRealTimer*)self;
	kcham ret;

	impl->curtime.q = (impl->stoptime.q != 0) ? impl->stoptime.q : k_cycle_long();
	impl->base.abstime = (double)impl->curtime.q * impl->tick;
	impl->base.runtime = (double)(impl->curtime.q - impl->basetime.q) * impl->tick;

	if (!impl->manual)
		impl->base.fps = (double)impl->frame.dw.l / (double)(impl->curtime.dw.l - impl->count);
	else
	{
		impl->fps_frame++;

		if ((impl->base.abstime - impl->fps_abs) >= 1.0)
		{
			impl->base.fps = (float)impl->fps_frame;
			impl->fps_abs = impl->base.abstime;
			impl->fps_frame = 0;
		}
	}

	impl->count = impl->curtime.dw.l;

	if (impl->base.fps < impl->cut)
	{
		impl->base.advance = (double)(impl->curtime.q - impl->lasttime.q) * impl->tick;
		ret = TRUE;
	}
	else
	{
		impl->base.advance = impl->cut * 0.001;
		ret = FALSE;
	}

	impl->lasttime.q = impl->curtime.q;
	impl->past = (int)(impl->base.advance * 1000.0);
	return ret;
}

/**
 * 타이머의 절대 시간.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	double.
 */
double k_timer_get_abs(kTimer* self)
{
	return self->abstime;
}

/**
 * 타이머의 시작 부터의 실행 시간.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	double.
 */
double k_timer_get_run(kTimer* self)
{
	return  self->runtime;
}

/**
 * 타이머 갱신의 시간 주기(Frame Per Second)
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	double.
 */
double k_timer_get_fps(kTimer* self)
{
	return self->fps;
}

/**
 * 타이머 갱신에 따른 경과 값.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	double.
 */
double k_timer_get_adv(kTimer* self)
{
	return self->advance;
}

/**
 * 타이머의 과다 수행에 따른 갱신 경과의 제한 값.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @return	double.
 */
double k_timer_get_cut(kTimer* self)
{
	kRealTimer* impl = (kRealTimer*)self;
	return impl->cut;
}

/**
 * 타이머 과다 수행에 따른 갱신 경과값의 설정.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @param	cut			제한 값.
 */
void k_timer_set_cut(kTimer* self, double cut)
{
	kRealTimer* impl = (kRealTimer*)self;
	impl->cut = cut;
}

/**
 * 타이머 매뉴얼 타입 FPS 측정.
 * @param [입력]	self	개체나 인터페이스의 자기 자신 값.
 * @param	value   	기능을 쓰려면 참으로 넣는다.
 */
void k_timer_set_manual(kTimer* self, kcham value)
{
	kRealTimer* impl = (kRealTimer*)self;
	impl->manual = value;
}

